package chat

import (
	"cade/src/core"
	"cade/src/core/evtmgr"
	"cade/src/core/log"
	"cade/src/core/wordsfilter"
	"cade/src/game/app/gconst"
	"cade/src/game/msg"
	Err "cade/src/proto/errorcode"
	"regexp"
	"sync"
	"sync/atomic"
	"time"
)

// ============================================================================

var rooms = make(map[int32]*room_t)
var rooms_by_name = make(map[string]*room_t)

var locker sync.RWMutex // room locker
var wg sync.WaitGroup   // room runner waiter

var seq_roomid int32

// ============================================================================

type room_t struct {
	Id      int32
	Name    string
	Num     int32
	players map[string]IPlayer // [uid]. players in the room
	Records *records_t         // recent chat records

	// own goroutine
	msgq chan func()
	quit chan int

	// hot words stats
	hotwords     []map[string]int32 // timespans of hotwords
	hotwords_idx int
}

// ============================================================================

func init() {
	// remove the player from his room if he logs out
	evtmgr.On(gconst.Evt_LoginOffline, func(args ...interface{}) {
		plr := args[0].(IPlayer)

		rm := find_room(plr.GetChatroomId())
		if rm != nil {
			rm.Push(func() {
				rm.RemovePlayer(plr)
			})
		}
	})
}

// ============================================================================

func new_room(name string) *room_t {
	return &room_t{
		Id:      atomic.AddInt32(&seq_roomid, 1),
		Name:    name,
		players: make(map[string]IPlayer),
		Records: new_records(c_MaxRecentRecords),

		msgq: make(chan func(), 10000),
		quit: make(chan int),

		hotwords: make([]map[string]int32, c_HotWordsInterval*60/c_RoomTimerInterval),
	}
}

func find_room(id int32) *room_t {
	locker.RLock()
	defer locker.RUnlock()

	return rooms[id]
}

func find_room_by_name(name string) *room_t {
	locker.RLock()
	defer locker.RUnlock()

	return rooms_by_name[name]
}

func add_room(rm *room_t) bool {
	locker.Lock()
	defer locker.Unlock()

	if rooms_by_name[rm.Name] != nil {
		return false
	}

	rooms[rm.Id] = rm
	rooms_by_name[rm.Name] = rm

	return true
}

func remove_room(rm *room_t) {
	locker.Lock()
	defer locker.Unlock()

	delete(rooms, rm.Id)
	delete(rooms_by_name, rm.Name)
}

// ============================================================================

func (self *room_t) Push(f func()) {
	defer func() { recover() }()

	select {
	case self.msgq <- f:
	default:
		log.Error("room msgq FULL. push discarded")
	}
}

func (self *room_t) run() {
	wg.Add(1)

	ticker := time.NewTicker(time.Second * 15)

	go func() {
		defer func() {
			close(self.msgq)
			ticker.Stop()
			wg.Done()
		}()

		for {
			select {
			case <-self.quit:
				return

			case <-ticker.C:
				core.PCall(self.on_timer)

			case f := <-self.msgq:
				core.PCall(f)
			}
		}
	}()
}

func (self *room_t) stop() {
	close(self.quit)
}

// ============================================================================

func (self *room_t) AddPlayer(plr IPlayer) int32 {
	// don't add multiple times
	if self.players[plr.GetId()] != nil {
		return Err.Chat_AlreadyInRoom
	}

	self.players[plr.GetId()] = plr
	plr.SetChatroomId(self.Id)
	self.Num++

	// send after addition
	self.broadcast(&msg.GS_ChatPlayerEnter{
		PlrName: plr.GetName(),
	})

	return Err.OK
}

func (self *room_t) RemovePlayer(plr IPlayer) int32 {
	// don't remove multiple times
	if self.players[plr.GetId()] == nil {
		return Err.Chat_NotInTheRoom
	}

	delete(self.players, plr.GetId())
	plr.SetChatroomId(0)
	self.Num--

	// check room destruction
	if self.Num <= 0 {
		self.stop()
		remove_room(self)
		return Err.OK
	}

	// send after deletion
	self.broadcast(&msg.GS_ChatPlayerLeave{
		PlrName: plr.GetName(),
	})

	return Err.OK
}

func (self *room_t) SendText(plr IPlayer, text string) int32 {
	// check text length
	if len(text) > c_MaxTextLength {
		return Err.Chat_TextTooLong
	}

	// check if we should execute a GM cmd
	if GMs[plr.GetName()] {
		gcmd := parse_gm_cmd(text)
		if gcmd != nil {
			if gcmd.execute(plr) {
				return Err.OK
			}
		}
	}

	// scan hot words
	self.scan_hotwords(text)

	// replace sensitive words
	text = wordsfilter.Filter(text)

	// add record
	rec := &chat_entry_t{
		Sender: plr.GetName(),
		Text:   text,
	}
	self.Records.Add(rec)

	// send
	self.broadcast(&msg.GS_ChatNewText{
		TextEntry: (*msg.ChatTextEntry)(rec),
	})

	return Err.OK
}

func (self *room_t) broadcast(m msg.Message) {
	for _, plr := range self.players {
		plr.SendMsg(m)
	}
}

func (self *room_t) on_timer() {
	// advance hotwords ptr
	L := len(self.hotwords)
	self.hotwords_idx = (self.hotwords_idx + 1) % L

	// reset
	self.hotwords[self.hotwords_idx] = make(map[string]int32)
}

func (self *room_t) scan_hotwords(text string) {
	// find current hotwords map
	m := self.hotwords[self.hotwords_idx]
	if m == nil {
		m = make(map[string]int32)
		self.hotwords[self.hotwords_idx] = m
	}

	// split words
	re := regexp.MustCompile(`\s+`)
	for _, w := range re.Split(text, -1) {
		m[w]++
	}
}

func (self *room_t) get_most_hot_word() (string, int32) {
	// sum word count of all timespans
	r := make(map[string]int32)
	for _, m := range self.hotwords {
		for w, n := range m {
			r[w] += n
		}
	}

	// find the most hot one
	hot := ""
	most := int32(0)
	for w, n := range r {
		if n > most {
			most = n
			hot = w
		}
	}

	return hot, most
}

func (self *room_t) tomsg_records() (ret []*msg.ChatTextEntry) {
	ret = make([]*msg.ChatTextEntry, 0, c_MaxRecentRecords)

	self.Records.Each(func(e *chat_entry_t) {
		ret = append(ret, (*msg.ChatTextEntry)(e))
	})

	return
}
